$input v_fragPos , v_normal, v_shadowcoord, v_texcoord0, v_tangent

#include "../../bgfx/examples/common/common.sh"


uniform vec4 u_viewPos;
uniform vec4 u_lightPos;
uniform vec4 u_lightColor;
uniform vec4 u_lightMode; // x = 0.0f:blinnphong   x = 1.0f:pbr

SAMPLER2D(s_texColor,  0);
SAMPLER2D(s_texNormal, 1);
SAMPLER2D(s_texAorm, 2);
SAMPLER2D(s_texShadowmap, 4);

#define PI 3.1415926

struct Light {
	vec3 color;
    vec4 position;
    vec3 ambient;
    vec3 diffuse;
    vec3 specular;
};

float DistributionGGX(vec3 N, vec3 H, float roughness)
{
    float a = roughness*roughness;
    float a2 = a*a;
    float NdotH = max(dot(N, H), 0.0);
    float NdotH2 = NdotH*NdotH;

    float nom   = a2;
    float denom = (NdotH2 * (a2 - 1.0) + 1.0);
    denom = PI * denom * denom;

    return nom / denom;
}

float GeometrySchlickGGX(float NdotV, float roughness)
{
    float r = (roughness + 1.0);
    float k = (r*r) / 8.0;

    float nom   = NdotV;
    float denom = NdotV * (1.0 - k) + k;

    return nom / denom;
}

float GeometrySmith(vec3 N, vec3 V, vec3 L, float roughness)
{
    float NdotV = max(dot(N, V), 0.0);
    float NdotL = max(dot(N, L), 0.0);
    float ggx2 = GeometrySchlickGGX(NdotV, roughness);
    float ggx1 = GeometrySchlickGGX(NdotL, roughness);

    return ggx1 * ggx2;
}

vec3 fresnelSchlick(float cosTheta, vec3 F0)
{
    return F0 + (1.0 - F0) * pow(clamp(1.0 - cosTheta, 0.0, 1.0), 5.0);
}

float hardShadow(sampler2D _sampler, vec4 shadowCoord, float bias)
{
	vec3 texCoord = shadowCoord.xyz / shadowCoord.w;
	// if depth of mesh(texCoord.z-bias) > depth of shadowmap then visibility = 0
	return step(texCoord.z-bias, unpackRgbaToFloat(texture2D(_sampler, texCoord.xy)));
}

float PCF(sampler2D _sampler, vec4 shadowCoord, float bias, vec2 texelSize)
{
	vec2 texCoord = shadowCoord.xy / shadowCoord.w;
	bool outside = any(greaterThan(texCoord, vec2_splat(1.0))) || any(lessThan(texCoord, vec2_splat(0.0)));
	if (outside)
	{
		return 1.0;
	}
	float result = 0.0;
	vec2 offset = texelSize * shadowCoord.w;
	for (int i = 0; i < 4; ++i) {
		float xOffset = -1.5 + float(i) * 1.0;
		for (int j = 0; j < 4; ++j) {
			float yOffset = -1.5 + float(j) * 1.0;	
			result += hardShadow(_sampler, shadowCoord + vec4(vec2(xOffset, yOffset) * offset, 0.0, 0.0), bias);
		}
	}
	return result / 16.0;
}

void main()
{
	float shadowMapBias = 0.005;
	vec2 texelSize = vec2_splat(1.0/512.0);
	float visibility = PCF(s_texShadowmap, v_shadowcoord, shadowMapBias, texelSize);
	
	//floor use blinnphong 
	if (u_lightMode.x < 0.5) {
		Light light;
		light.position = mul(u_view, u_lightPos);
		light.color = u_lightColor.xyz;
		light.ambient = vec3(0.1, 0.1, 0.1);
		light.diffuse = vec3(0.5, 0.5, 0.5);
		light.specular = vec3 (1.0, 1.0, 1.0);
		float shininess = 16.0;
	
		//vec4 materialColor = toLinear(texture2D(s_texColor, vec2(v_texcoord0.x, 1.0f - v_texcoord0.y)));
		vec4 materialColor = vec4(1.0, 1.0, 1.0, 1.0);

		vec3 normal = normalize(v_normal);

		// ambient
		vec3 ambient = light.ambient * materialColor.xyz ;

		//diffuse
		vec3 lightDir = normalize(light.position.xyz - v_fragPos);
		float diff = max(dot(normal, lightDir), 0.0);
		vec3 diffuse = light.diffuse * diff * materialColor.xyz ;  

		// specular
		vec3 view = normalize(-v_fragPos);
		vec3 h = normalize(view + lightDir);
		float spec = pow(max(dot(normal,h), 0.0), shininess); //api:step  prevent specular at back
		// with the specular, the visual effect is a bit strange
		vec3 specular = light.specular * spec * light.color * 0.0;  

		gl_FragColor.xyz =ambient + (diffuse + specular) * visibility ;
		gl_FragColor.w = 1.0;
		gl_FragColor = toGamma(gl_FragColor);
	}
	// stone use pbr
	if (u_lightMode.x > 0.5) {
		Light light;
		light.position = mul(u_view, u_lightPos);
		light.color = u_lightColor.xyz;
		light.ambient = vec3(0.03f, 0.03f, 0.03f);
	
		// material parameter
		vec4 texMaterialColor = toLinear(texture2D(s_texColor, vec2(v_texcoord0.x, 1.0f - v_texcoord0.y)));
		vec4 texAorm = texture2D(s_texAorm, vec2(v_texcoord0.x, 1.0f - v_texcoord0.y)); //y:roughness  z:metallic
		vec3 albedo = texMaterialColor.xyz;
		float roughness = texAorm.y;
		float metallic = texAorm.z;
		float ao = texAorm.x;
	
		// load normal from texture
		vec3 texNormal = texture2D(s_texNormal, vec2(v_texcoord0.x, 1.0f - v_texcoord0.y)).xyz;
		texNormal = normalize(texNormal * 2.0 - 1.0);
		vec3 v_bitangent = normalize(cross(v_normal, v_tangent));
		mat3 TBN = mat3(v_tangent, v_bitangent, v_normal);
		vec3 N = normalize(mul(TBN , texNormal));
		N = mul(u_modelView, vec4(N, 0.0) ).xyz;
		N = normalize(N);

 		vec3 F0 = vec3(0.056, 0.056, 0.056);  //stone's f0
		F0 = mix(F0, albedo, metallic);
		vec3 V = normalize(-v_fragPos);
		vec3 L = normalize(light.position.xyz - v_fragPos) ;
		vec3 H = normalize(V + L);
		vec3 radiance = light.color;

		float NdotL = max(dot(N, L), 0.0);  

		// BRDF-specular
		float D = DistributionGGX(N, H, roughness);   
		float G = GeometrySmith(N, V, L, roughness);      
		vec3 F = fresnelSchlick(clamp(dot(H, V), 0.0, 1.0), F0);
		vec3 Ks = F;
		vec3 numerator = D * G * F; 
		float denominator = 4.0 * max(dot(N, V), 0.0) * NdotL + 0.0001; // prevent divide by zero
		vec3 specular = numerator / denominator * radiance * NdotL;  //Ks = F no need to multiply Ks again

		// BRDF-diffuse
		vec3 Kd = vec3(1.0, 1.0, 1.0) - Ks;
		Kd = Kd * (1.0 - metallic);
		vec3 diffuse = Kd * albedo / PI * radiance * NdotL;   

		// ambient
		vec3 ambient = light.ambient * texMaterialColor.xyz * ao;
	
		// Cook-Torrance BRDF + ambient
		vec3 color = (diffuse + specular) * visibility + ambient;
		color = color / (color + vec3(1.0, 1.0, 1.0));    //tonemapping

		gl_FragColor.xyz = color * 10.0; // origin color is too dark
		gl_FragColor.w = 1.0;
		gl_FragColor = toGamma(gl_FragColor);		
	}

}
